✅(UI) Durable Objectに保存するようにした後、数字更新までに時間がかかるようになり気持ちよさが減った
(UI) Durable Objectに保存するようにした後、数字更新までに時間がかかるようになり気持ちよさが減った
UI上では更新して、裏であとから送るようにするなどしてカウントのUIのレスポンスをlocalstorage並みにしたい。useOptimisticが使えないか?あるいは他の方法は?
GPT-5.icon
送信キューが欲しくなるケース
連打/スパム的な操作をバッチ化してDO呼び出し回数を減らしたい
一時的な失敗時に自動再送・指数バックオフをしたい
タブを閉じる瞬間の未送信分を送っておきたい(sendBeacon/keepalive)
同一操作の重複送信をサーバ側で吸収したい(idempotency key)
オフライン→復帰で溜めた分を流したい(localStorage等に永続)
複数タブの同一リソース操作をまとめたい
簡易な(=厳密性を犠牲にした)楽観的UI更新システムを導入した
https://gyazo.com/4148f5f4b8ad38297ea7dd5ade446a90https://gyazo.com/8c43d16138499948335c0bbb45b9502e
修正前/修正後
動画だとよくわからないが、カウンタが即時反映されるようになっている
状態フローの詳細
1. ユーザーアクション発生
code:typescript
handleIncrement(bookId) {
// 1. 即座にpending mutationに追加(楽観的更新)
const mutation = addMutation('increment', bookId);
// 2. UI更新(viewBooks自動再計算)
// 3. サーバーに非同期送信
sendMutation(mutation); // WebSocket優先、HTTP fallback
}
2. サーバー応答受信
code:typescript
onWebSocketMessage(message) {
if (message.type === 'books_updated') {
// サーバー状態は権威あるため、pending全削除
clearPendingMutations();
updateBaseState(message.books, message.version);
// → viewBooks = applyMutations(newBaseBooks, [])
}
}
mutationId設計
code:typescript
// 数値IDで確実な順序保証
function generateMutationId(): number {
const timestamp = performance.now();
return Math.floor(timestamp * 1000) + (++mutationCounter % 1000);
}
// 各mutationは一意ID + clientIdで重複排除
type Mutation = {
id: number; // 順序保証用数値ID
clientId: string; // クライアント識別
type: 'increment' | 'decrement';
bookId: string | number;
timestamp: number;
}
通信レイヤー設計
code:typescript
// WebSocket優先、HTTP自動fallback
sendMutation(mutation) {
if (webSocket?.readyState === WebSocket.OPEN) {
webSocket.send(JSON.stringify({
action: mutation.type,
bookId: mutation.bookId,
mutationId: mutation.id,
clientId: mutation.clientId
}));
} else {
// WebSocket接続なし/エラー時は即座にHTTPへfallback
sendBookUpdate(mutation.type, { ...mutation });
}
}
最初は厳密なACK+rebaseシステムを採用したが、挙動が怪しかった。解体していくとWebSocketの無限ループが発覚した。その後実装をシンプル版に変更した。これで問題があればまた戻せばいい基素.icon